﻿using System.Collections.Generic;
using UnityEngine;
using Devil;


#if UNITY_EDITOR
using UnityEditor;
#endif

namespace GameToolkit
{
    [ExecuteInEditMode, AddComponentMenu("")]
    public class DebugCamera : MonoBehaviour
    {
        public static DebugCamera Current { get; private set; }
        public static bool ForceReset { get; set; }
#if UNITY_EDITOR
        [System.Serializable, ProjectConfig("GameToolkit/Culling Camera")]
        class CullingInvokeSystem : ProjectConfiguration.Configurable
        {
            //public bool debug;

            public LayerMask debugLayers = -1;

            [HideInInspector]
            public bool debugPrecullAction;
            [HideInInspector]
            public bool debugCullBounding;
            [HideInInspector]
            public bool boundingSelectable;
            [HideInInspector]
            public int boundingDistance = 1;
            [HideInInspector]
            public float boundingHandleSize = 1f;

            public override void OnGUI()
            {
                base.OnGUI();
                //debug = EditorGUILayout.Toggle("Debug Culling Bounds", debug);
                EditorGUI.indentLevel++;
                EditorGUI.BeginDisabledGroup(debugLayers.value == 0);
                //if (DebugCamera.debuging)
                //{
                //EditorGUILayout.LabelField("Bounds Count", cullcam.CullLength.ToString());
                EditorGUI.BeginChangeCheck();
                debugPrecullAction = EditorGUILayout.Toggle("Debug PreCull Action", debugPrecullAction);
                var cull = EditorApplication.isPlayingOrWillChangePlaymode ? _debugCullBounding : debugCullBounding;
                cull = EditorGUILayout.Toggle("Debug Cull Bounding", cull);
                if (EditorApplication.isPlayingOrWillChangePlaymode)
                    _debugCullBounding = cull;
                else
                    debugCullBounding = cull;
                boundingDistance = EditorGUILayout.IntSlider("Debug Cull Distance", boundingDistance, 0, 5);
                boundingHandleSize = EditorGUILayout.Slider("Debug Handle Size", boundingHandleSize, 0.1f, 2f);
                boundingSelectable = EditorGUILayout.Toggle("Bouding Selectable", boundingSelectable);
                if (boundingSelectable)
                    EditorGUILayout.HelpBox("当鼠标悬停于裁剪对象上时，按下【A/D】切换重叠对象，【SPACE】选中目标", MessageType.Info, true);
                //}
                EditorGUI.EndDisabledGroup();
                EditorGUI.indentLevel--;
                if (EditorGUI.EndChangeCheck())
                    SetDirty();
            }
        }

        static CullingInvokeSystem _config;
        static CullingInvokeSystem config
        {
            get
            {
                if (_config == null)
                    _config = ProjectConfiguration.GetConfigurable<CullingInvokeSystem>();
                return _config;
            }
        }
        //static bool _debuging;
        public static bool debuging
        {
            get { return config.debugLayers.value != 0;/* _debuging;*/ }
            //set
            //{
            //    if (_debuging != value)
            //    {
            //        _debuging = value;
            //        EditorPrefs.SetBool("Culling.Debug", value);
            //    }
            //}
        }

        //static float _handleSize = 1;
        public static float handleSize
        {
            get { return config.boundingHandleSize; }
            //set
            //{
            //    if (!Mathf.Approximately(_handleSize, value))
            //    {
            //        _handleSize = value;
            //        EditorPrefs.SetFloat("Culling.HandleSize", _handleSize);
            //    }
            //}
        }

        //static bool _debugSelectBounding;
        public static bool debugSelectBounding
        {
            get { return config.boundingSelectable; }
            //set
            //{
            //    if (_debugSelectBounding != value)
            //    {
            //        _debugSelectBounding = value;
            //        EditorPrefs.SetBool("Culling.Selectable", value);
            //    }
            //}
        }

        //static bool _debugPreCullAction;
        public static bool debugPreCullAction
        {
            get { return config.debugPrecullAction; }
            //set
            //{
            //    if (_debugPreCullAction != value)
            //    {
            //        _debugPreCullAction = value;
            //        EditorPrefs.SetBool("Culling.DebugPreAction", value);
            //    }
            //}
        }
        static bool _debugCullBounding;
        public static bool debugCullBounding
        {
            get { return EditorApplication.isPlayingOrWillChangePlaymode ? _debugCullBounding : config.debugCullBounding; }
            //set
            //{
            //    if (_debugCullBounding != value)
            //    {
            //        _debugCullBounding = value;
            //        if (!EditorApplication.isPlayingOrWillChangePlaymode)
            //            EditorPrefs.SetBool("Culling.DebugBounding", value);
            //    }
            //}

        }

        //static int _debugCullDistance;
        public static int debugCullDistance
        {
            get { return config.boundingDistance; }
            //set
            //{
            //    if (_debugCullDistance != value)
            //    {
            //        _debugCullDistance = value;
            //        EditorPrefs.SetInt("Culling.DebugDistance", value);
            //    }
            //}
        }

        public static int debugLayers
        {
            get { return config.debugLayers; }
        }

        static List<GameObject> sPrefabStage = new List<GameObject>();

        [InitializeOnLoadMethod]
        static void InitDebugData()
        {
            //_debuging = EditorPrefs.GetBool("Culling.Debug", true);
            //_debugSelectBounding = EditorPrefs.GetBool("Culling.Selectable", true);
            //_debugPreCullAction = EditorPrefs.GetBool("Culling.DebugPreAction", true);
            //_debugCullBounding = EditorPrefs.GetBool("Culling.DebugBounding", false);
            //_debugCullDistance = EditorPrefs.GetInt("Culling.DebugDistance", 1);
            SceneView.duringSceneGui += SceneGUI;
            EditorApplication.playModeStateChanged += ChangePlayMode;
        }

        private static void ChangePlayMode(PlayModeStateChange obj)
        {
            if (obj == PlayModeStateChange.EnteredPlayMode)
                _debugCullBounding = false;
            //else if (obj == PlayModeStateChange.EnteredEditMode)
            //    _debugCullBounding = config.debugCullBounding;// EditorPrefs.GetBool("Culling.DebugBounding", false);
        }
        public static bool BeginGroup(string category, bool drop, int space = 5, params GUILayoutOption[] options)
        {
            EditorGUILayout.BeginVertical("helpbox", options);
            EditorGUI.indentLevel++;
            drop = EditorGUILayout.Foldout(drop, category, true, "IN TitleText");
            EditorGUI.indentLevel--;
            if (drop && space > 0)
                GUILayout.Space(space);
            return drop;
        }


        static RaycastStack mRaycasts = new RaycastStack();
        static RaycastStack mRaycastCache = new RaycastStack();
        public static ICullingBounds SelectedBounds { get { return mRaycasts.SelectedRaycast; } }
        public static RaycastStack RaycastsList { get { return mRaycasts; } }
        public static void BeginRaycast(Vector3 origin)
        {
            lock (mRaycastCache)
            {
                mRaycastCache.BeginStack(origin);
            }
        }

        public static void Raycast(CullingState stat, Vector3 point)
        {
            lock (mRaycastCache)
            {
                mRaycastCache.Push(stat, point);
            }
        }

        public static void EndRaycast()
        {
            lock (mRaycastCache)
            {
                if (mRaycastCache.Version != mRaycasts.Version)
                {
                    var tmp = mRaycastCache;
                    mRaycastCache = mRaycasts;
                    mRaycasts = tmp;
                }
            }
        }

        static void SceneGUI(SceneView scene)
        {
            DebugCamera decam = scene.camera.GetComponent<DebugCamera>();

            if (debuging && decam == null)
            {
                decam = scene.camera.gameObject.AddComponent<DebugCamera>();
            }
            else if (!debuging && decam != null)
            {
                GlobalUtil.Destroy(decam);
                decam = null;
            }
            Current = decam;
            if (!debuging)
                return;
            Camera cam = null;
#if UNITY_2021_1_OR_NEWER
            var stage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
#else
            var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
#endif
            if (stage != null)
            {
                sPrefabStage.Clear();
                stage.scene.GetRootGameObjects(sPrefabStage);
                for (int i = 0; cam == null && i < sPrefabStage.Count; i++)
                {
                    cam = sPrefabStage[i].GetComponent<Camera>();
                }
            }
            else
            {
                var sel = Selection.activeGameObject;
                cam = sel == null ? null : sel.GetComponent<Camera>();
            }
            if (cam == null)
            {
                cam = Camera.main;
            }
            if (cam != null)
                decam.SetDebugCamera(cam, ForceReset);

            if (decam.mDebugCull != null)
            {
                Camera cullCam;
                if (debugCullBounding)
                {
                    cullCam = scene.camera;
                }
                else
                {
                    cullCam = decam.mDebugCull.AttachedCamera;
                }
                if (cullCam != null && cullCam != decam.mDebugCull.ActiveCamera)
                {
                    decam.mDebugCull.SetCullingCamera(cullCam);
                }
            }

            ForceReset = false;
            if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.C && cam != null)
            {
                Selection.activeGameObject = cam.gameObject;
            }
        }

        Camera mCamera;

        //[System.NonSerialized]
        //public ICollection<IPreCullAction> preCullActions;


        Camera mDebugCamera;
        CullingCamera mDebugCull;

        public void SetDebugCamera(Camera cam, bool forceReset = false)
        {
            if (forceReset || (mDebugCamera != cam))
            {
                mDebugCamera = cam;
                if (mCamera == null)
                    mCamera = GetComponent<Camera>();

                mDebugCull = cam == null ? null : cam.GetComponent<CullingCamera>();
            }
        }

        private void OnDisable()
        {
        }

        private void OnPreRender()
        {
        }

        private void OnPreCull()
        {
            if (mCamera == null)
                mCamera = GetComponent<Camera>();
            if (mDebugCull != null && mCamera != null && mDebugCull.PreCullActions != null)
            {
                foreach (var cull in mDebugCull.PreCullActions)
                {
                    cull.OnPreCullAction(mCamera);
                }
            }
        }

        [MenuItem("Game Toolkit/Select SceneView Camera")]
        static void SelectSceneCamera()
        {
            var scenes = SceneView.sceneViews;
            var scene = scenes == null || scenes.Count == 0 ? null : scenes[0] as SceneView;
            if (scene != null && scene.camera != null)
                Selection.activeGameObject = scene.camera.gameObject;
        }

        public class RaycastStack
        {
            int _version;
            List<CullingState> _raycasts;
            int _selIndex;
            Vector3 _rayOriginal;
            float _rayDistance;
            public int Version { get { return _version; } }

            public IList<CullingState> Raycasts { get { return _raycasts; } }

            public int SelectedIndex
            {
                get
                {
                    if (_raycasts.Count < 1)
                        return 0;
                    var i = _selIndex % _raycasts.Count;
                    if (i < 0)
                        i += _raycasts.Count;
                    return i;
                }
            }

            public ICullingBounds SelectedRaycast
            {
                get
                {
                    if (_raycasts.Count == 0)
                        return null;
                    var i = _selIndex % _raycasts.Count;
                    if (i < 0)
                        i += _raycasts.Count;
                    return _raycasts[i].bounds;
                }
            }

            public string GetRaycastTitle(int offset)
            {
                if (_raycasts.Count > 0)
                {
                    var i = (_selIndex + offset) % _raycasts.Count;
                    if (i < 0)
                        i += _raycasts.Count;
                    return ((ICullingBoundsDebug)_raycasts[i].bounds).Title;
                }
                else
                {
                    return "";
                }
            }

            public void SelectAt(int index)
            {
                _selIndex = index % _raycasts.Count;
            }

            public void SelectNext(int offset)
            {
                if (offset != 0 && _raycasts.Count > 1)
                {
                    _selIndex = (_selIndex + offset) % _raycasts.Count;
                }
            }

            public RaycastStack()
            {
                _version = 0;
                _raycasts = new List<CullingState>();
            }

            public void BeginStack(Vector3 rayOriginal)
            {
                _version = 0;
                _raycasts.Clear();
                _rayOriginal = rayOriginal;
                _rayDistance = 0;
            }

            public void Push(CullingState stat, Vector3 point)
            {
                _raycasts.Add(stat);
                _version = MathExt.Hash(_version, stat.bounds == null ? 0 : stat.bounds.GetHashCode());
                var dis = Vector3.SqrMagnitude(point - _rayOriginal);
                if(_raycasts.Count == 0 || dis < _rayDistance)
                {
                    _rayDistance = dis;
                    _selIndex = _raycasts.Count - 1;
                }
            }
        }
#endif
        }
}